/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023.
 * Description: rtos feature caller trace
 * Author: wangjun
 * Create: 2023-09-18
 */

#ifdef CONFIG_RTOS_DEBUG_CALLER_RELO_BASED_TRACE
#define MODNAME_LEN	128
#define FUNCNAME_LEN 128
#define MAX_INPUT_LEN 256
#define RELOAD_PREFIX "__com_"
#define MAX_LIST_LEN 1000

struct module_filter_node {
	struct list_head list;
	char modname[MODNAME_LEN];
	char funcname[FUNCNAME_LEN];
};

static DEFINE_MUTEX(module_filter_list_lock);
static LIST_HEAD(module_filter_list);


static void *proc_module_filter_seq_start(struct seq_file *m, loff_t *pos)
{
	mutex_lock(&module_filter_list_lock);
	return seq_list_start(&module_filter_list, *pos);
}

static void *proc_module_filter_seq_next(struct seq_file *m, void *p, loff_t *pos)
{
	return seq_list_next(p, &module_filter_list, pos);
}

static void proc_module_filter_seq_stop(struct seq_file *m, void *p)
{
	mutex_unlock(&module_filter_list_lock);
}

static int proc_module_filter_seq_show(struct seq_file *m, void *p)
{
	struct module_filter_node *node = list_entry(p, struct module_filter_node, list);

	seq_printf(m, "%s,%s\n", node->modname, node->funcname);
	return 0;
}

static const struct seq_operations proc_module_filter_list_seq_ops = {
	.start = proc_module_filter_seq_start,
	.next = proc_module_filter_seq_next,
	.stop = proc_module_filter_seq_stop,
	.show = proc_module_filter_seq_show,
};

static int module_filter_proc_open(struct inode *inode, struct file *file)
{
	return seq_open(file, &proc_module_filter_list_seq_ops);
}

static int parse_config(const char *buf, char *modname, char *funcname)
{
	const char *start, *start1;
	char *end, *end1;

	start = buf;
	end = strchr(buf, ',');
	if (!end || (end - start) <= 0 || (end - start) >= MODNAME_LEN)
		return -1;

	start1 = end + 1;
	end1 = strchr(buf, '\n');
	if (!end1 || (end1 - start1) <= 0 || (end1 - start1) >= FUNCNAME_LEN)
		return -1;

	strncpy(modname, start, end - start);
	strncpy(funcname, start1, end1 - start1);
	return 0;
}

int rtos_mod_filter_write(char *mod_name, char *func_name)
{
	struct module_filter_node *node, *node_tmp;
	int list_len = 0;
	struct list_head *head = &module_filter_list;

	if (mod_name == NULL && func_name == NULL) {
		mutex_lock(&module_filter_list_lock);
		list_for_each_entry_safe(node, node_tmp, &module_filter_list, list) {
			list_del(&node->list);
			kfree(node);
		}
		mutex_unlock(&module_filter_list_lock);
		return 0;
	}
	if (mod_name == NULL || func_name == NULL) {
		pr_err("mod_name is NULL or func_name is NULL\n");
		return -EINVAL;
	}

	node = kzalloc(sizeof(struct module_filter_node), GFP_KERNEL);
	if (!node)
		return -ENOMEM;

	strncpy(node->modname, mod_name, MODNAME_LEN - 1);
	strncpy(node->funcname, func_name, FUNCNAME_LEN - 1);

	mutex_lock(&module_filter_list_lock);
	while (head->next != &module_filter_list) {
		if (strcmp(list_entry(head->next, struct module_filter_node, list)->modname, node->modname) == 0 &&
		    strcmp(list_entry(head->next, struct module_filter_node, list)->funcname, node->funcname) == 0) {
			kfree(node);
			mutex_unlock(&module_filter_list_lock);
			return 0;
		}
		list_len++;
		head = head->next;
	}
	if (list_len >= MAX_LIST_LEN) {
		pr_err("module_filter_list too long\n");
		kfree(node);
		mutex_unlock(&module_filter_list_lock);
		return -EPERM;
	}
	list_add_tail(&node->list, &module_filter_list);
	mutex_unlock(&module_filter_list_lock);
	return 0;
}
EXPORT_SYMBOL_GPL(rtos_mod_filter_write);

static ssize_t module_filter_proc_write(struct file *file, const char __user *buffer,
					size_t count, loff_t *pos)
{
	int ret;
	char str[MAX_INPUT_LEN] = { 0 };
	char modname[MODNAME_LEN] = { 0 };
	char funcname[FUNCNAME_LEN] = { 0 };
	struct module_filter_node *node, *tmp;

	if (count >= MAX_INPUT_LEN) {
		pr_err("count=%zu inval\n", count);
		return -EINVAL;
	}
	if (count <= 1) {
		mutex_lock(&module_filter_list_lock);
		list_for_each_entry_safe(node, tmp, &module_filter_list, list) {
			list_del(&node->list);
			kfree(node);
		}
		mutex_unlock(&module_filter_list_lock);
		return count;
	}

	if (copy_from_user(str, buffer, count)) {
		pr_err("copy_from_user failed\n");
		return -EFAULT;
	}

	if (parse_config(str, modname, funcname))
		return -EINVAL;

	ret = rtos_mod_filter_write(modname, funcname);
	if (ret)
		return ret;
	return count;
}

static const struct proc_ops module_filter_proc_ops = {
	.proc_open	= module_filter_proc_open,
	.proc_read	= seq_read,
	.proc_lseek	= seq_lseek,
	.proc_release	= seq_release,
	.proc_write	= module_filter_proc_write,
};

static int __init proc_module_filter_init(void)
{
	proc_create("module_filter", 0640, NULL, &module_filter_proc_ops);
	return 0;
}
module_init(proc_module_filter_init);

static DEFINE_MUTEX(module_filter_blocklist_lock);
static LIST_HEAD(module_filter_blocklist);

int rtos_mod_filter_blocklist_write(char *mod_name, char *func_name)
{
	struct module_filter_node *node, *node_tmp;
	int list_len = 0;
	struct list_head *head = &module_filter_blocklist;

	if (mod_name == NULL && func_name == NULL) {
		mutex_lock(&module_filter_blocklist_lock);
		list_for_each_entry_safe(node, node_tmp, &module_filter_blocklist, list) {
			list_del(&node->list);
			kfree(node);
		}
		mutex_unlock(&module_filter_blocklist_lock);
		return 0;
	}
	if (mod_name == NULL || func_name == NULL) {
		pr_err("mod_name is NULL or func_name is NULL\n");
		return -EINVAL;
	}

	node = kzalloc(sizeof(struct module_filter_node), GFP_KERNEL);
	if (!node)
		return -ENOMEM;

	strncpy(node->modname, mod_name, MODNAME_LEN - 1);
	strncpy(node->funcname, func_name, FUNCNAME_LEN - 1);

	mutex_lock(&module_filter_blocklist_lock);
	while (head->next != &module_filter_blocklist) {
		if (strcmp(list_entry(head->next, struct module_filter_node, list)->modname, node->modname) == 0 &&
		    strcmp(list_entry(head->next, struct module_filter_node, list)->funcname, node->funcname) == 0) {
			kfree(node);
			mutex_unlock(&module_filter_blocklist_lock);
			return 0;
		}
		list_len++;
		head = head->next;
	}
	if (list_len >= MAX_LIST_LEN) {
		pr_err("module_filter_blocklist too long\n");
		kfree(node);
		mutex_unlock(&module_filter_blocklist_lock);
		return -EPERM;
	}

	list_add_tail(&node->list, &module_filter_blocklist);
	mutex_unlock(&module_filter_blocklist_lock);
	return 0;
}
EXPORT_SYMBOL_GPL(rtos_mod_filter_blocklist_write);

static int module_filter(struct module *mod, const char *funcname)
{
	int matched;
	struct module_filter_node *node;

	matched = 0;
	mutex_lock(&module_filter_list_lock);
	list_for_each_entry(node, &module_filter_list, list) {
		if (!node)
			break;
		if (strcmp(node->modname, mod->name) == 0 &&
		    strcmp(node->funcname, funcname) == 0) {
			matched = 1;
			break;
		}
		if (strcmp(node->modname, "all") == 0 &&
		    strcmp(node->funcname, funcname) == 0) {
			matched = 1;
			break;
		}
	}
	mutex_unlock(&module_filter_list_lock);

	/* Module symbols in the blocklist are not traced */
	mutex_lock(&module_filter_blocklist_lock);
	list_for_each_entry(node, &module_filter_blocklist, list) {
		if (!node)
			break;
		if (strcmp(node->modname, mod->name) == 0 &&
		    strcmp(node->funcname, funcname) == 0) {
			matched = 0;
			break;
		}
	}
	mutex_unlock(&module_filter_blocklist_lock);

	return matched;
}

static const struct kernel_symbol *find_adapt_symbol(const char *name,
		struct module **owner,
		const s32 **crc,
		bool gplok)
{
	enum mod_license license;
	const struct kernel_symbol *sym;
	char *adapt_name = kmalloc(strlen(name) + strlen(RELOAD_PREFIX) + 1, GFP_KERNEL);

	if (!adapt_name)
		return NULL;

	strncpy(adapt_name, RELOAD_PREFIX, strlen(RELOAD_PREFIX));
	strncpy(adapt_name + strlen(RELOAD_PREFIX), name, strlen(name));

	adapt_name[strlen(name) + strlen(RELOAD_PREFIX)] = '\0';

	sym = find_symbol(adapt_name, owner, crc, &license, gplok, true);
	kfree(adapt_name);
	return sym;
}

static void get_adapt_symbol(struct module *mod, const char *name, const s32 *crc,
			     const struct kernel_symbol **sym, struct module **owner)
{
	struct module *adapt_owner;
	const struct kernel_symbol *adapt_sym;

	if (module_filter(mod, name)) {
		adapt_sym = find_adapt_symbol(name, &adapt_owner, &crc,
					      !(mod->taints & (1 << TAINT_PROPRIETARY_MODULE)));
		if (adapt_sym) {
			*sym = adapt_sym;
			*owner = adapt_owner;
		}
	}
}
bool rtos_is_kernel_rodata(const void *x)
{
	return is_kernel_rodata((uintptr_t)x);
}
EXPORT_SYMBOL_GPL(rtos_is_kernel_rodata);
#endif
